<!DOCTYPE html>
<html>
	<!--
	/*!
	 * VisualEditor keyboard event logger page
	 *
	 * @copyright 2011-2020 VisualEditor Team and others; see http://ve.mit-license.org
	 */
	-->
	<head>
		<meta charset="utf-8">
		<title>Keyboard Event Logger</title>
		<style>
			html {
				margin: 0;
				padding: 0;
				height: 100%;
			}

			body {
				margin: 1em;
				padding: 0;
				height: 100%;
			}

			.ve-demo-form {
				width: 100%;
				min-height: 2em;
			}

			.ve-demo-content {
				border: 1px solid gray;
				width: 100%;
				min-height: 2em;
				max-height: 15%;
				overflow-y: auto;
			}

			.ve-demo-log {
				margin-top: 1em;
				border: 1px solid gray;
				width: 100%;
				height: 80%;
				white-space: pre;
				font-family: monospace, monospace; /* Support: Blink, Gecko, Webkit, see T176636 */
				padding: 0;
				overflow-y: auto;
			}
		</style>
		<link rel=stylesheet href="../../lib/oojs-ui/oojs-ui-apex.css" class="stylesheet-ltr stylesheet-read">
	</head>
	<body>
		<form class="ve-demo-form">
			IME identifier (name and version):
			<input type="text" class="ve-demo-identifier">
			<input type="submit" value="Start Logging Keyboard Events">
		</form>
		<div class="ve-demo-content" contenteditable="true"></div>
		<textarea class="ve-demo-log"></textarea>

		<script src="../../lib/jquery/jquery.js"></script>
		<script src="../../lib/oojs/oojs.jquery.js"></script>
		<script src="../../lib/oojs-ui/oojs-ui-core.js"></script>
		<script src="../../lib/unicodejs/unicodejs.js"></script>
		<script src="../../src/ve.js"></script>
		<script src="../../src/ve.utils.js"></script>
		<script src="../../src/ve.EventSequencer.js"></script>
		<script>
			( function () {
				var startTime,
					/* eslint-disable no-jquery/no-global-selector */
					sel = document.getSelection(),
					$form = $( '.ve-demo-form' ),
					$content = $( '.ve-demo-content' ).addClass( 'oo-ui-element-hidden' ),
					content = $content[ 0 ],
					$log = $( '.ve-demo-log' ).val( '' ),
					$imeIdentifier = $( '.ve-demo-identifier' ),
					/* eslint-enable no-jquery/no-global-selector */
					oldHtml = '',
					oldAnchorPosition = null,
					oldFocusPosition = null,
					comments,
					log = [];

				function getPosition( node, offset ) {
					var position;
					if ( node === null ) {
						return null;
					}
					position = [ offset ];
					while ( node.parentNode !== null && node !== content ) {
						position.splice(
							0,
							0,
							Array.prototype.indexOf.call(
								node.parentNode.childNodes,
								node
							)
						);
						node = node.parentNode;
					}
					return position;
				}

				function eventToObject( e ) {
					// TODO: flesh this out more
					if ( !e || e.keyCode === undefined ) {
						return {};
					} else {
						return { keyCode: e.keyCode };
					}
				}

				function showLog() {
					var i, len, jsons = [];
					jsons.push( JSON.stringify( comments ) );
					for ( i = 0, len = log.length; i < len; i++ ) {
						jsons.push( JSON.stringify( log[ i ] ) );
					}
					$log.val( '[\n\t' + jsons.join( ',\n\t' ) + '\n]' );
					$log[ 0 ].scrollTop = $log[ 0 ].scrollHeight;
				}

				function logWrite( val ) {
					log.push( val );
					showLog();
				}

				function logEvent( methodName, eventName, e ) {
					var args = [],
						msUtc = ( new Date().getTime() - startTime ) / 1000,
						newHtml = $content.html(),
						newAnchorPosition = getPosition( sel.anchorNode, sel.anchorOffset ),
						newFocusPosition = getPosition( sel.focusNode, sel.focusOffset );

					if ( oldHtml !== newHtml ) {
						logWrite( {
							seq: log.length,
							time: msUtc,
							action: 'changeText',
							args: [ newHtml ]
						} );
						oldHtml = newHtml;
					}
					if (
						JSON.stringify( oldAnchorPosition ) !== JSON.stringify( newAnchorPosition ) ||
						JSON.stringify( oldFocusPosition ) !== JSON.stringify( newFocusPosition )
					) {
						logWrite( {
							seq: log.length,
							time: msUtc,
							action: 'changeSel',
							args: [
								newAnchorPosition,
								newFocusPosition
							]
						} );
						oldAnchorPosition = newAnchorPosition;
						oldFocusPosition = newFocusPosition;
					}
					if ( eventName !== undefined ) {
						args.push( eventName );
						args.push( eventToObject( e ) );
					}

					logWrite( {
						seq: log.length,
						time: msUtc,
						action: methodName,
						args: args
					} );
				}

				function makeEventHandler( eventName ) {
					return function ( e ) {
						logEvent( 'sendEvent', eventName, e );
					};
				}

				function makeAfterLoopHandler() {
					return function () {
						logEvent( 'endLoop' );
					};
				}

				/** @param {jQuery.Event} e Halo form submit event */
				function start( e ) {
					e.preventDefault();

					comments = {
						imeIdentifier: $imeIdentifier.val(),
						userAgent: navigator.userAgent,
						startDom: $content.html()
					};

					$form.addClass( 'oo-ui-element-hidden' );
					$content.removeClass( 'oo-ui-element-hidden' ).trigger( 'focus' );
				}

				( function () {
					var i, len,
						handlers = {},
						eventNames = [
							'compositionstart',
							'compositionend',
							'keydown',
							'keyup',
							'keypress',
							'input'
						];

					for ( i = 0, len = eventNames.length; i < len; i++ ) {
						handlers[ eventNames[ i ] ] =
							makeEventHandler( eventNames[ i ] );
					}

					// eslint-disable no-new
					new ve.EventSequencer( eventNames )
						.attach( $content )
						.on( handlers )
						.afterLoop( makeAfterLoopHandler() );

					$( document ).on( 'selectstart', makeEventHandler( 'selectstart' ) );
					$( document ).on( 'selectionchange', makeEventHandler( 'selectionchange' ) );

					startTime = new Date().getTime();

					$form.on( 'submit', start );

					$log.on( 'click', function () {
						if ( this.select ) {
							this.select();
						}
					} );
				}() );

			}() );
		</script>
	</body>
</html>
